package pri.damai.xiaowu.common.core.dict;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import pri.damai.xiaowu.common.core.dict.anno.DictField;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 处理字典值工具类
 *
 * @Desc 示例: 字典值字段为 dictId 或者 dict ,字典中文名字段为 dictName。
 * @Author YXF
 * @Date 2021/7/31 9:57
 */
@Slf4j
@AllArgsConstructor
public class DictUtils {

    DictService dictService;

    public static final String ID = "Id";
    public static final String SUFFIX_DICT_FIELD = "Name";

    public static final String GET = "get";
    public static final String SET = "set";


    /**
     * 处理字典值
     *
     * @param data 需处理的实体
     * @apiNote [附加描述]
     * @author YXF
     * @date 2021/7/31 11:09
     */
    public <T> void handleDict(T data) {

        if (Objects.isNull(data)) {
            return;
        }

        if (data instanceof List) {
            List<Object> list = (List<Object>) data;
            if (list.size() == 0) {
                return;
            }
            this.bitchSetDictName(list);
        } else {
            setDictName(data);
        }
    }

    private <T> void bitchSetDictName(List<T> list) {
        Class<?> aClass = list.get(0).getClass();
        List<Field> fields = this.getOjbDictField(aClass);

        Map<String, List<String>> dictTypeValues = list.stream()
                .flatMap(obj -> fields.stream()
                        .filter(field -> Objects.nonNull(field.getAnnotation(DictField.class)))
                        .map(s -> {
                            DictService.DictQuery dictQuery = new DictService.DictQuery();
                            s.setAccessible(true);
                            DictField dictField = s.getAnnotation(DictField.class);
                            String dictType = dictField.dictType();
                            dictQuery.setDictType(dictType);
                            try {
                                Object value = s.get(obj);
                                if (value instanceof List || value instanceof Set) {
                                    Collection<String> collection = (Collection<String>) value;
                                    dictQuery.setDictValues(CollectionUtil.newArrayList(collection));
                                    return dictQuery;
                                } else if (value instanceof String) {
                                    dictQuery.setDictValues(CollectionUtil.newArrayList((String) value));
                                    return dictQuery;
                                } else {
                                    log.warn("标注为 DictField 的字段,不是 List || Set || String. 跳过处理. {}:{}", s, value);
                                    return dictQuery;
                                }
                            } catch (IllegalAccessException e) {
                                throw new RuntimeException(e);
                            }
                        })).collect(
                        Collectors.groupingBy(DictService.DictQuery::getDictType,
                                Collectors.collectingAndThen(Collectors.toList(),
                                        s -> s.stream().flatMap(values -> values.getDictValues().stream())
                                                .collect(Collectors.toList()))));

        List<DictService.DictQuery> dictQueryList = dictTypeValues.keySet().stream().map(key -> {
            List<String> dictValues = dictTypeValues.get(key);
            DictService.DictQuery dictQuery = new DictService.DictQuery();
            dictQuery.setDictType(key);
            dictQuery.setDictValues(dictValues);
            return dictQuery;
        }).collect(Collectors.toList());

        Map<String, List<DictDTO>> dictTypeMap = dictService.dictInfoMap(dictQueryList);

        for (T obj : list) {
            Map<Field, Object> map = this.getObjDictValue(obj);
            this.setObjDictName(obj, map, dictTypeMap);
        }
    }

    private <T> void setObjDictName(Object data, Map<Field, Object> fieldMap, Map<String, List<DictDTO>> dictTypeMap) {

        Map<String, Map<String, String>> typeValueNameMap = toDictTypeValueNameMap(dictTypeMap);

        for (Field field : fieldMap.keySet()) {
            Object value = fieldMap.get(field);
            if (Objects.isNull(value)) {
                continue;
            }

            try {
                Method setDictNameMethod = this.getDictNameSetMethod(field, data.getClass());
                setDictNameMethod.setAccessible(true);
                DictField dictField = field.getAnnotation(DictField.class);
                String dictType = dictField.dictType();
                String splitSymbol = dictField.splitSymbol();

                if (value instanceof List) {
                    List<String> valueList = (List<String>) value;
                    List<String> names = this.getDictNames(typeValueNameMap, dictType, valueList);
                    setDictNameMethod.invoke(data, names);
                } else if (value instanceof String) {
                    String valueStr = (String) value;
                    String nameStr = this.splitAndJoinValue(typeValueNameMap, dictType, valueStr, splitSymbol);
                    setDictNameMethod.invoke(data, nameStr);
                }

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private Map<String, Map<String, String>> toDictTypeValueNameMap(Map<String, List<DictDTO>> dictTypeMap) {

        if (Objects.isNull(dictTypeMap) || dictTypeMap.isEmpty()) {
            return new HashMap<>();
        }

        return dictTypeMap.keySet().stream().collect(Collectors.toMap(Function.identity(), s -> {
            List<DictDTO> dictDTOS = dictTypeMap.get(s);
            if (CollectionUtil.isEmpty(dictDTOS)) {
                return new HashMap<String, String>();
            } else {
                return dictDTOS.stream().collect(Collectors.toMap(DictDTO::getDictId, DictDTO::getDictName, (k1, k2) -> k1));
            }
        }));
    }

    private String getDictName(Map<String, Map<String, String>> dictTypeValueNameMap, String dictType, String dictValue) {
        Map<String, String> valueNameMap = dictTypeValueNameMap.getOrDefault(dictType, new HashMap<>());
        return valueNameMap.getOrDefault(dictValue, "");
    }

    private List<String> getDictNames(Map<String, Map<String, String>> dictTypeValueNameMap, String dictType, List<String> dictValues) {
        if (CollectionUtil.isEmpty(dictValues) || MapUtil.isEmpty(dictTypeValueNameMap)) {
            return Lists.newArrayList();
        }
        return dictValues.stream()
                .map(s -> getDictName(dictTypeValueNameMap, dictType, s))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    @SuppressWarnings("unchecked")
    private <T> void setDictName(T data) {

        Class<?> dataClass = data.getClass();
        Field[] fields = dataClass.getDeclaredFields();

        // 获取字段与字段值的映射关系
        Map<Field, DictService.DictQuery> fieldMap = Arrays.stream(fields)
                .filter(field -> Objects.nonNull(field.getAnnotation(DictField.class)))
                .collect(Collectors.toMap(field -> field, field -> {
                    field.setAccessible(true);
                    DictField dictFieldAnno = field.getAnnotation(DictField.class);
                    String dictType = dictFieldAnno.dictType();
                    String splitSymbol = dictFieldAnno.splitSymbol();
                    DictService.DictQuery dictQuery = new DictService.DictQuery();
                    dictQuery.setDictType(dictType);
                    try {

                        Object value = field.get(data);
                        if (value instanceof List || value instanceof Set) {
                            Collection<String> collection = (Collection<String>) value;
                            dictQuery.setDictValues(collection);
                        } else if (value instanceof String) {
                            if (StringUtils.isNotBlank(splitSymbol)) {
                                ArrayList<String> valueList = Lists.newArrayList(((String) value).split(splitSymbol));
                                dictQuery.setDictValues(valueList);
                            } else {
                                dictQuery.setDictValues(Lists.newArrayList((String) value));
                            }
                        } else {
                            log.warn("标注为 DictField 的字段,不是 List || Set || String. 跳过处理.");
                        }
                    } catch (Exception e) {
                        log.error("类中获取字段字典值失败. 类:{},字段:{}", dataClass, field);
                        throw new RuntimeException(e);
                    }
                    return dictQuery;
                }, (k1, k2) -> k2));


        Map<String, List<DictDTO>> dictInfoMap = dictService.dictInfoMap(Lists.newArrayList(fieldMap.values()));

        Map<String, Map<String, String>> dictTypeValueNameMap = toDictTypeValueNameMap(dictInfoMap);

        // 设置字典值
        for (Field field : fieldMap.keySet()) {
            try {
                Object value = field.get(data);
                DictField annotation = field.getAnnotation(DictField.class);
                String dictType = annotation.dictType();
                String splitSymbol = annotation.splitSymbol();
                Method setDictNameMethod = this.getDictNameSetMethod(field, dataClass);
                setDictNameMethod.setAccessible(true);

                if (value instanceof List) {
                    List<String> valueList = (List<String>) value;
                    List<String> dictNames = this.getDictNames(dictTypeValueNameMap, dictType, valueList);
                    setDictNameMethod.invoke(data, dictNames);
                } else if (value instanceof String) {
                    String nameStr = this.splitAndJoinValue(dictTypeValueNameMap, dictType, (String) value, splitSymbol);
                    setDictNameMethod.invoke(data, nameStr);
                }

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private String splitAndJoinValue(Map<String, Map<String, String>> dictTypeValueNameMap, String dictType, String value, String split) {
        String nameStr;
        if (StringUtils.isNotBlank(split)) {
            ArrayList<String> valueList = Lists.newArrayList(value.split(split));
            nameStr = getDictNames(dictTypeValueNameMap, dictType, valueList).stream().collect(Collectors.joining(split));
        } else {
            nameStr = getDictName(dictTypeValueNameMap, dictType, value);
        }
        return nameStr;
    }

    private Map<Field, Object> getObjDictValue(Object ele) {
        Class<?> dataClass = ele.getClass();
        Field[] fields = dataClass.getDeclaredFields();
        return Arrays.stream(fields)
                .filter(field -> Objects.nonNull(field.getAnnotation(DictField.class)))
                .collect(Collectors.toMap(field -> field, field -> {
                    try {
                        field.setAccessible(true);
                        Object value = field.get(ele);
                        return Objects.isNull(value) ? "" : value;
                    } catch (Exception e) {
                        log.error("类中获取字段字典值失败. 类:{},字段:{}", dataClass, field);
                        throw new RuntimeException(e);
                    }
                }, (k1, k2) -> k2));
    }

    private List<Field> getOjbDictField(Class<?> dataClass) {
        Field[] fields = dataClass.getDeclaredFields();
        return Arrays.stream(fields).filter(field -> Objects.nonNull(field.getAnnotation(DictField.class)))
                .collect(Collectors.toList());
    }

    private Method getDictNameSetMethod(Field field, Class<?> dataClass) {
        String dictNameMethodName = getDictNameSetMethodName(field.getName());
        Class<?> aClass = field.getType();
        try {
            return dataClass.getDeclaredMethod(dictNameMethodName, aClass);
        } catch (NoSuchMethodException e) {
            log.error("未在类中找到设置字典字段名称方法. 类:{},字段:{}", dataClass, field);
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取设置字典的方法名
     *
     * @param dictIdFieldName 字典Id字段名称
     * @return java.lang.String
     * @apiNote [附加描述]
     * @author YXF
     * @date 2021/7/31 11:09
     */
    private String getDictNameSetMethodName(String dictIdFieldName) {
        if (dictIdFieldName.endsWith(ID)) {
            dictIdFieldName = dictIdFieldName.substring(0, dictIdFieldName.length() - 2);
        }
        return SET + firstLetterToUpper(dictIdFieldName) + DictUtils.SUFFIX_DICT_FIELD;
    }

    /**
     * 获取方法名
     *
     * @param fieldName 字段名称
     * @return java.lang.String
     * @apiNote [附加描述]
     * @author YXF
     * @date 2021/7/31 11:07
     */
    private String getGetMethodName(String fieldName) {
        return DictUtils.GET + firstLetterToUpper(fieldName);
    }

    /**
     * 首字母大写
     *
     * @param str 字符
     * @return java.lang.String
     * @apiNote [附加描述]
     * @author YXF
     * @date 2021/7/31 11:03
     */
    private String firstLetterToUpper(String str) {
        if (Objects.isNull(str) || str.trim().isEmpty()) {
            return str;
        }

        char[] chars = str.toCharArray();
        chars[0] -= 32;
        return String.valueOf(chars);
    }

}
